package com.tc.preheat;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.ClassUtils;

import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 程序预热的监听
 */
public class PreHeatListener implements SmartLifecycle {

	private static final Logger logger = LoggerFactory.getLogger(PreHeatListener.class);

	private static AtomicBoolean preheated = new AtomicBoolean(false);
	
	private int phase = 0; // highest priority

	private volatile boolean autoStartup = true;
	
	private volatile boolean running = false; 
	
	@Autowired
	private volatile ApplicationContext context;

	@Override
	public void start() {
		if (preheated.get()) {
			return;
		}
		long start = System.currentTimeMillis();
		// 1.config in properties
		ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
		preheatFromConfig(defaultClassLoader);
		// 2. config in beans
		preheatFromBean(defaultClassLoader, context);
		preheated.set(true);
		logger.info(">>>>>>>>>>>>preheat cost time :" + (System.currentTimeMillis() - start) + " ms");	
	}

	private void preheatFromBean(ClassLoader defaultClassLoader, ApplicationContext context) {
		Map<String, PreheatApi> preheatListeners = context.getBeansOfType(PreheatApi.class);
		if (preheatListeners != null && preheatListeners.size() > 0) {
			for (PreheatApi warmUpListener : preheatListeners.values()) {
				preheat(warmUpListener, defaultClassLoader);
			}
		}
	}

	private void preheatFromConfig(ClassLoader defaultClassLoader) {
		try {
			Enumeration<URL> urls = defaultClassLoader.getResources("META-INF/preheat");
			if (urls != null) {
				while (urls.hasMoreElements()) {
					String classFullName = getContent(urls.nextElement());
					try {
						Class<?> listenerClass = Class.forName(classFullName, true, defaultClassLoader);
						Object listener = BeanUtils.instantiateClass(listenerClass);
						if (listener instanceof PreheatApi) {
							preheat((PreheatApi) listener, defaultClassLoader);
						} else {
							logger.warn(">>>>" + classFullName + " not implement  PreheatApi interface.");
						}
					} catch (Exception e) {
						logger.warn("preheat  instantiateClass error " + e.getMessage());
					}
				}
			}
		} catch (Exception e) {
			logger.warn("preheatFromConfig error " + e.getMessage());
		}
	}

	/**
	 * 预先加载class
	 */
	private void preheat(PreheatApi preheatApi, ClassLoader defaultClassLoader) {
		List<String> classNameList = preheatApi.preheatClass();
		if ( null != classNameList && classNameList.size() > 0) {
			for (String classOrPackageName : classNameList) {
				if (classOrPackageName.contains(".*")) {
					loadClassFromPackage(defaultClassLoader, classOrPackageName);
				} else {
					loadClass(defaultClassLoader, classOrPackageName);
				}
			}
		}

		try {
			preheatApi.preheatInfrastrcuture();
			preheatApi.preheatApp();
		} catch (Exception e) {
			logger.warn(">>>>preheatInfrastrcuture or preheatApp error:" + e.getMessage());
		}
	}


	private static Class<?> loadClass(ClassLoader defaultClassLoader, String className) {
		try {
			return defaultClassLoader.loadClass(className);
		} catch (Throwable e) {
			logger.info(">>>>loadClass:" + className + " not found." + e.getMessage());
		}
		return null;
	}
	/**
	 * 预先加载包
	 */
	private static void loadClassFromPackage(ClassLoader defaultClassLoader, String classOrPackageName) {
		String pkgName = StringUtils.removeEnd(classOrPackageName, ".*");
		String packageDir = pkgName.replaceAll("\\.", "/");

		List<Class<?>> classList = new ArrayList<Class<?>>();
		try {
			Enumeration<URL> urls = defaultClassLoader.getResources(packageDir);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				if (url != null) {
					String protocol = url.getProtocol();
					String pkgPath = url.getPath();
					// System.out.println("+++++++++++++++++++++++++protocol:" + protocol + ",,, path:" + pkgPath);
					if ("file".equals(protocol)) {
						// 本地自己可见的代码
						findClassNameFromLocal(defaultClassLoader, classList, pkgName, pkgPath, true);
					} else if ("jar".equals(protocol)) {
						// 引用第三方jar的代码
						findClassNameFromJar(defaultClassLoader, classList, pkgName, url, true);
					}
				}
			}
		} catch (IOException e) {
			logger.warn(">>>>loadClassFromPackage error:" + e.getMessage());
		}
		// logger.info(">>>>pkgName:" + pkgName + ",classList:" + classList);
	}

	/**
	 * 三方Jar类库的引用
	 * 
	 * @param clazzList
	 * @param pkgName
	 * @param url
	 * @param isRecursive
	 * @throws IOException
	 */
	public static void findClassNameFromJar(ClassLoader defaultClassLoader, List<Class<?>> clazzList, String pkgName,
			URL url, boolean isRecursive) throws IOException {
		JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
		JarFile jarFile = jarURLConnection.getJarFile();
		// System.out.println(">>>>>>>>>>>>>>jarFile:" + jarFile.getName());

		Enumeration<JarEntry> jarEntries = jarFile.entries();
		while (jarEntries.hasMoreElements()) {
			JarEntry jarEntry = jarEntries.nextElement();
			String jarEntryName = jarEntry.getName(); // 类似：sun/security/internal/interfaces/TlsMasterSecret.class
			// System.out.println(">>>>>>>>>>>>>>jarEntryName:" + jarEntryName);

			String clazzName = jarEntryName.replace("/", ".");
			int endIndex = clazzName.lastIndexOf(".");
			String prefix = null;
			String prefix_name = null;
			if (endIndex > 0) {
				prefix_name = clazzName.substring(0, endIndex);
				endIndex = prefix_name.lastIndexOf(".");
				if (endIndex > 0) {
					prefix = prefix_name.substring(0, endIndex);
				}
			}
			// System.out.println("prefix:" + prefix);

			if (prefix != null && jarEntryName.endsWith(".class")) {
				if (prefix.equals(pkgName)) {
					addClassName(defaultClassLoader, clazzList, prefix_name);
				} else if (isRecursive && prefix.startsWith(pkgName)) {
					// 遍历子包名：子类
					addClassName(defaultClassLoader, clazzList, prefix_name);
				}
			}
		}
	}

	/**
	 * 本地类的引用
	 * 
	 * @param clazzList
	 * @param pkgName
	 * @param pkgPath
	 * @param isRecursive
	 */
	public static void findClassNameFromLocal(ClassLoader defaultClassLoader, List<Class<?>> clazzList, String pkgName,
			String pkgPath, boolean isRecursive) {
		File[] files = filterClassFiles(pkgPath);// 过滤出.class文件及文件夹
		// System.out.println("files:" + ((files == null) ? "null" : "length=" + files.length));
		for (File f : files) {
			String fileName = f.getName();
			if (f.isFile()) {
				// .class 文件的情况
				String clazzName = getClassName(pkgName, fileName);
				addClassName(defaultClassLoader, clazzList, clazzName);
			} else {
				// 文件夹的情况
				if (isRecursive) {
					// 需要继续查找该文件夹/包名下的类
					String subPkgName = pkgName + "." + fileName;
					String subPkgPath = pkgPath + "/" + fileName;
					findClassNameFromLocal(defaultClassLoader, clazzList, subPkgName, subPkgPath, true);
				}
			}
		}
	}

	public static void addClassName(ClassLoader defaultClassLoader, List<Class<?>> clazzList, String clazzName) {
		if (clazzName != null) {
			Class<?> clazz = loadClass(defaultClassLoader, clazzName);
			if (clazz != null) {
				clazzList.add(clazz);
			}
		}
	}

	public static File[] filterClassFiles(String pkgPath) {
		if (pkgPath == null) {
			return null;
		}

		FileFilter filter = new FileFilter() {
			public boolean accept(File file) {
				return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
			}
		};
		return new File(pkgPath).listFiles(filter);
	}

	public static String getClassName(String pkgName, String fileName) {
		int endIndex = fileName.lastIndexOf(".");
		String clazz = null;
		if (endIndex >= 0) {
			clazz = fileName.substring(0, endIndex);
		}

		String clazzName = null;
		if (clazz != null) {
			clazzName = pkgName + "." + clazz;
		}
		return clazzName;
	}

	public static String getContent(URL url) {
		StringBuilder sb = new StringBuilder();
		try {
			if (url != null) {
				Reader reader = new InputStreamReader(new BufferedInputStream(url.openStream()));
				int c;
				while ((c = reader.read()) != -1) {
					sb.append((char) c);
				}
				reader.close();
			}
		} catch (Exception e) {
		}

		return sb.toString();
	}

	
	@Override
	public void stop() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean isRunning() {
		// TODO Auto-generated method stub
		return running;
	}

	@Override
	public int getPhase() {
		return phase;
	}

	@Override
	public boolean isAutoStartup() {
		return autoStartup;
	}

	@Override
	public void stop(Runnable callback) {
		// TODO Auto-generated method stub
		
	}

}
